package com.example.front.arrange;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * TaskBeanProcessor
 *
 * @author liming2
 * @date 2023/11/20 13:21
 */
@Slf4j
@Configuration
public class TaskBeanProcessor implements ApplicationListener<ContextRefreshedEvent> {

    private DefaultListableBeanFactory beanFactory;

    private ApplicationContext context;

    private Map<String, ScannedGenericBeanDefinition> allTaskBeanDefinition = new HashMap<>();
    private Map<String, ScannedGenericBeanDefinition> allRootBeanDefinition = new HashMap<>();

    private Map<String, AbstractTask> allTaskBeans = new HashMap<>();
    private Map<String, AbstractTask> allRootBeans = new HashMap<>();

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        context = event.getApplicationContext();
        if (!(context instanceof GenericApplicationContext)) {
            return;
        }
        beanFactory = ((GenericApplicationContext) context).getDefaultListableBeanFactory();

        Map<Class<? extends AbstractRootTask>, Map<String, Config>> taskConfigMap = new HashMap<>();

        String[] beanNames = context.getBeanNamesForType(AbstractTask.class);

        Map<String, AbstractRootTask> rootBeans = context.getBeansOfType(AbstractRootTask.class);

        for (String beanName : beanNames) {
            AbstractTask task = (AbstractTask) context.getBean(beanName);
            ScannedGenericBeanDefinition beanDefinition = (ScannedGenericBeanDefinition) beanFactory.getBeanDefinition(beanName);
            if (task instanceof AbstractRootTask) {
                allRootBeans.put(beanName, task);
                allRootBeanDefinition.put(beanName, beanDefinition);
            } else {
                allTaskBeans.put(beanName, task);
                allTaskBeanDefinition.put(beanName, beanDefinition);
            }
        }
        for (Map.Entry<String, AbstractRootTask> entry : rootBeans.entrySet()) {
            List<ScannedGenericBeanDefinition> groupBeanDefinitionList = new ArrayList<>();
            getBeanDefinitionList(Collections.singletonList(entry.getValue().getClass()), groupBeanDefinitionList);

            Map<String, Config> configMap = new HashMap<>();
            taskConfigMap.put(entry.getValue().getClass(), configMap);
            for (ScannedGenericBeanDefinition beanDefinition : groupBeanDefinitionList) {
                Map<String, Object> annotationAttributesMap = beanDefinition.getMetadata().getAnnotationAttributes(TaskDependent.class.getName());
                AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(annotationAttributesMap);
                String taskName = beanDefinition.getBeanClassName();
                this.setConfig(taskName, configMap, annotationAttributes);
            }
            groupBeanDefinitionList.add(allRootBeanDefinition.get(entry.getKey()));
        }

        Map<Class<? extends AbstractRootTask>, List<Config>> arrangeConfigMap =
                taskConfigMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> new ArrayList<>(entry.getValue().values())));

        diff(arrangeConfigMap);

        // check 循环依赖
        for (Map.Entry<Class<? extends AbstractRootTask>, List<Config>> entry : arrangeConfigMap.entrySet()) {
            List<Config> configList = entry.getValue();
            for (Config config : configList) {
                List<Config> parentList = new ArrayList<>();
                for (Integer parentId : config.getParentIdList()) {
                    configList.stream().filter(c -> parentId.intValue() == c.getId()).findFirst().ifPresent(parentList::add);
                }
                config.setParentConfigList(parentList);
            }
            for (Config config : configList) {
                for (Config parent : config.getParentConfigList()) {
                    if (parent.getParentConfigList().stream().anyMatch(p -> config == p)) {
                        log.error("{} 和 {} 循环依赖", config.getTaskName(), parent.getTaskName());
                        throw new RuntimeException("任务配置错误");
                    }
                }
            }
        }
        TaskArrangeSupport.setArrangeConfig(arrangeConfigMap);
    }

    private void diff(Map<Class<? extends AbstractRootTask>, List<Config>> arrangeConfigMap) {
        if (arrangeConfigMap.size() < 2) {
            return;
        }
        List<Map.Entry<Class<? extends AbstractRootTask>, List<Config>>> arrangeConfigList = new ArrayList<>(arrangeConfigMap.entrySet());
        for (int i = 0; i < arrangeConfigList.size(); i++) {
            for (int j = i + 1; j < arrangeConfigList.size(); j++) {
                if (!Collections.disjoint(arrangeConfigList.get(i).getValue(), arrangeConfigList.get(j).getValue())) {
                    log.error("任务树:{}和{}有交集", arrangeConfigList.get(i).getKey().getSimpleName(), arrangeConfigList.get(j).getKey().getSimpleName());
                    throw new RuntimeException("任务树:" + arrangeConfigList.get(i).getKey().getSimpleName() + "和" + arrangeConfigList.get(j).getKey().getSimpleName() + "有交集");
                }
            }
        }
    }

    private void getBeanDefinitionList(List<Class<?>> parentList, List<ScannedGenericBeanDefinition> groupBeanDefinitionList) {
        List<Class<?>> newParentList = new ArrayList<>();
        for (Class<?> parent : parentList) {
            for (Map.Entry<String, ScannedGenericBeanDefinition> definition : allTaskBeanDefinition.entrySet()) {
                Map<String, Object> annotationAttributesMap = definition.getValue().getMetadata().getAnnotationAttributes(TaskDependent.class.getName());
                AnnotationAttributes annotationAttributes = AnnotationAttributes.fromMap(annotationAttributesMap);
                Class<?>[] dependencies;
                if (!CollectionUtils.isEmpty(annotationAttributes) &&
                        (dependencies = annotationAttributes.getClassArray("dependencies")).length > 0) {
                    if (Arrays.asList(dependencies).contains(parent)) {
                        groupBeanDefinitionList.add(definition.getValue());
                        newParentList.add(allTaskBeans.get(definition.getKey()).getClass());
                    }
                }
            }
        }
        if (newParentList.isEmpty()) {
            return;
        }
        getBeanDefinitionList(newParentList, groupBeanDefinitionList);
    }

    private void setConfig(String taskName, Map<String, Config> configMap, AnnotationAttributes annotationAttributes) {
        Config config;
        if (configMap.containsKey(taskName)) {
            config = configMap.get(taskName);
        } else {
            config = new Config();
            config.setId(configMap.size() + 1);
            config.setTaskName(taskName);
            configMap.put(taskName, config);
        }
        Class<?>[] dependencies;
        if (!CollectionUtils.isEmpty(annotationAttributes) &&
                (dependencies = annotationAttributes.getClassArray("dependencies")).length > 0) {
            List<Integer> parentIdList = new ArrayList<>();
            for (Class<?> dependent : dependencies) {
                String dependTaskName = dependent.getName();
                if (configMap.containsKey(dependTaskName)) {
                    parentIdList.add(configMap.get(dependTaskName).getId());
                } else {
                    Config parentConfig = new Config();
                    parentConfig.setId(configMap.size() + 1);
                    parentConfig.setTaskName(dependTaskName);
                    configMap.put(dependTaskName, parentConfig);
                    parentIdList.add(parentConfig.getId());
                }
            }
            config.setParentIdList(parentIdList);
        } else {
            //config.setParentIdList(Collections.singletonList(-1));
            throw new RuntimeException(taskName + "没有指定依赖的父节点");
        }
    }

}
